using System;
using System.Collections.Generic;
using System.Linq;
using Infinity.Common;

namespace Infinity.Expressions.Utilities
{
	/// <summary>
	/// Rule type for cast manager
	/// </summary>
	public enum CastManagerRuleType
	{
		Sequential,
		Combinatorical
	}

	/// <summary>
	/// Manager class for type casting
	/// </summary>
	public class CastManager : ICastManager
	{
		protected Dictionary<Type, List<Type>> m_CastingRules { get; set; }

		public CastManager()
		{
			m_CastingRules = new Dictionary<Type, List<Type>>();
		}

		public Type[] GetRuleFrom(Type type)
		{
			Ensure.NotNull(type);

			List<Type> result;
			if (m_CastingRules.TryGetValue(type, out result))
				return result.ToArray();

			return Type.EmptyTypes;
		}

		/// <summary>
		/// It returns true if there is casting rule between 'from' -> 'to'.
		/// </summary>
		public bool CanCast(Type from, Type to)
		{
			Ensure.NotNull("from", from);
			Ensure.NotNull("to", to);

			return from == to || GetRuleFrom(from).FirstOrDefault(x => x == to) != null;
		}

		/// <summary>
		/// It adds new type casting rule. If isSymmetric is true then rule will be from &lt;-&gt; to, otherwise from -&gt; to.
		/// </summary>
		public void AddRule(Type from, Type to, bool isSymmetric = true)
		{
			AddRule(from, to);
			if (isSymmetric)
				AddRule(to, from);
		}

		/// <summary>
		/// It adds a new type casting rule.
		/// </summary>
		private void AddRule(Type from, Type to)
		{
			Ensure.NotNull(from);
			Ensure.NotNull(to);

			if (from == to)
				Error.ThrowException("It is a trivial type casting rule!");

			List<Type> result;
			if (!m_CastingRules.TryGetValue(from, out result))
			{
				result = new List<Type>();
				m_CastingRules.Add(from, result);
			}

			if (!result.Contains(to))
				result.Add(to);
			else
				Error.ThrowException("This rule have already been contained!");
		}

		/// <summary>
		/// It clears rules.
		/// </summary>
		public void ClearRules()
		{
			m_CastingRules.Clear();
		}

		/// <summary>
		/// It adds new type casting rule. If isSymmetric is true then rule will be from &lt;-&gt; to, otherwise from -&gt; to.
		/// </summary>
		public void AddRule<TFrom, TTo>(bool isSymmetric = true)
		{
			AddRule(typeof(TFrom), typeof(TTo), isSymmetric);
		}

		/// <summary>
		/// It adds a new type casting rule.
		/// </summary>
		public void AddRules(Type[] from, Type[] to, CastManagerRuleType ruleType = CastManagerRuleType.Combinatorical)
		{
			Ensure.NotNull(from);
			Ensure.NotNull(to);

			if (ruleType == CastManagerRuleType.Combinatorical)
			{
				foreach (var f in from)
				{
					foreach (var t in to)
					{
						if (Ensure.NotNull(f) != t)
							AddRule(f, t);
					}
				}
			}
			else if (ruleType == CastManagerRuleType.Sequential)
			{
				foreach (var type in from.Zip(to, Tuple.Create))
				{
					AddRule(type.Item1, type.Item2);
				}
			}
		}
	}
}